package com.lagou.edu.factory;

import com.lagou.edu.anno.*;
import com.lagou.edu.utils.JsonUtils;
import com.lagou.edu.utils.PackageScanner;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author 应癫
 *
 * 工厂类，生产对象（使用反射技术）
 */
public class BeanFactory {

    /**
     * 任务一：读取解析xml，通过反射技术实例化对象并且存储待用（map集合）
     * 任务二：对外提供获取实例对象的接口（根据id获取）
     */

    private static Map<String,Object> beanMap = new HashMap<>();  // 存储对象


    static {
        // 扫描com.lagou.edu下面的所有类
        PackageScanner packageScanner = new PackageScanner("com.lagou.edu");
        packageScanner.doFileScan();
        Map<String, Class<?>> classMap = packageScanner.getClassMap();

        //1. 第一步：首先实例化所有类上有注解的类（不注入属性）
        //判断这些类上是否有@Service  @Component  @Repository注解
        //有的话实例化并放进存储对象map中
        for (Map.Entry<String, Class<?>> classEntry : classMap.entrySet()) {
//            System.out.println(stringClassEntry.getKey());
//            System.out.println(stringClassEntry.getValue());
            String clazzName = classEntry.getKey();
            Class<?> clazz = classEntry.getValue();

            String customBeanId = "";
            if (clazz.isAnnotationPresent(Service.class)) {
                customBeanId = clazz.getAnnotation(Service.class).beanId();

            } else if (clazz.isAnnotationPresent(Component.class)) {
                customBeanId = clazz.getAnnotation(Component.class).beanId();
            } else if (clazz.isAnnotationPresent(Repository.class)) {
                customBeanId = clazz.getAnnotation(Repository.class).beanId();
            }

            if (clazz.isAnnotationPresent(Service.class) || clazz.isAnnotationPresent(Component.class) || clazz.isAnnotationPresent(Repository.class)) {
                //如果没定义customId,就用默认的驼峰名设置beanId
                if ("".equals(customBeanId)) {
                    try {
                        beanMap.put(clazzName, clazz.getDeclaredConstructor().newInstance());
                    } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
                        e.printStackTrace();
                    }
                }
                //如果定义了customId,就用定义的名称作为beanId
                else {
                    try {
                        beanMap.put(customBeanId, clazz.getDeclaredConstructor().newInstance());
                    } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
                        e.printStackTrace();
                    }
                }
            }
        }

        // 2. 第二步：遍历所有实例化的类并填充依赖属性
        for (Map.Entry<String, Object> beanEntry : beanMap.entrySet()) {
            Object parentObject = beanEntry.getValue();
            Field[] declaredFields = parentObject.getClass().getDeclaredFields();
            //判断类下面是否有属性有@Autowired注解
            for (Field field : declaredFields) {
                if (field.isAnnotationPresent(Autowired.class)) {
                    String customBeanId = field.getAnnotation(Autowired.class).customBeanId();

                    String beanId = "";
                    //如果没指定就默认使用field的名称进行寻找
                    if ("".equals(customBeanId)) {
                        beanId = field.getName();
                    }
                    //如果指定了就是用指定的名称进行寻找
                    else {
                        beanId = customBeanId;
                    }

                    //获取所依赖的实例
                    // 找到field对应的实例之后使用set方法进行注入
                    Object existingObject = beanMap.get(beanId);
                    // 遍历父对象中的所有方法，找到"set" + name
                    Method[] methods = parentObject.getClass().getMethods();
                    for (Method method : methods) {
                        if (method.getName().equalsIgnoreCase("set" + field.getName())) {  // 该方法就是 setAccountDao(AccountDao accountDao)
                            try {
                                //调用set方法进行依赖注入
                                method.invoke(parentObject, existingObject);
                            } catch (IllegalAccessException | InvocationTargetException e) {
                                e.printStackTrace();
                            }
                        }
                    }
                }
            }

        }

        // 3. 第三步，bean创建完成后获取事务代理对象
        for (Map.Entry<String, Object> beanEntry : beanMap.entrySet()) {
            //扫描所有实例，判断是否有@Transactional方法
            Object parentObject = beanEntry.getValue();
            boolean hasTransactional = false;
            //判断类上面是否有transactional注解
            if (parentObject.getClass().isAnnotationPresent(Transactional.class)) {
                hasTransactional = true;
            }
            //判断方法上面是否有transactional注解
            Method[] declaredMethods = parentObject.getClass().getDeclaredMethods();
            for (Method method : declaredMethods) {
                if (method.isAnnotationPresent(Transactional.class)) {
                    hasTransactional = true;
                    break;
                }
            }

            //如果这个类有需要事务控制的方法，就利用代理工厂直接生成代理对象
            if (hasTransactional) {
                ProxyFactory proxyFactory = (ProxyFactory) beanMap.get("proxyFactory");
                //使用jdk动态代理（只能规定一整个方法进行事务控制）
//                Object proxyObject =  proxyFactory.getJdkProxy(parentObject);
                //使用cglib动态代理（可以规定哪一个方法进行事务控制）
                Object proxyObject =  proxyFactory.getCglibProxy(parentObject);
                //将代理对象实例替换掉原先生成的实例
                beanMap.put(beanEntry.getKey(), proxyObject);
            }

        }


    }

    // 任务二：对外提供获取实例对象的接口（根据id获取）
    public static Object getBean(String id) {
        return beanMap.get(id);
    }


}
